import {
  CancellationToken,
  CompletionContext,
  CompletionItem,
  CompletionItemProvider,
  CompletionList,
  ExtensionContext,
  languages,
  Position,
  ProviderResult,
  TextDocument,
} from 'vscode';
import { last, nth } from 'lodash';
import { ContextTag, ContextItem, ContextModel } from './context';
import { Model } from './model';
import { eachEndTag, eachTag, helperVariableReg, withEndTag, withTag } from './reg-exp';
import { ModelItem } from './interface';
import { ModelCompletionItem } from './model-completion-item';

/**
 * 模板模型自动填充
 *
 * @author chitanda
 * @date 2022-01-10 18:01:54
 * @export
 * @class ModelCompletionProvider
 * @implements {CompletionItemProvider}
 */
export class ModelCompletionProvider implements CompletionItemProvider {
  /**
   * 模型处理程序
   *
   * @author chitanda
   * @date 2022-01-13 17:01:59
   * @protected
   */
  protected model = Model.getInstance();
  /**
   * 当次计算下的所有顶级标签域
   *
   * @author chitanda
   * @date 2022-01-13 17:01:26
   * @protected
   * @type {ContextItem[]}
   */
  protected contexts: ContextItem[] = [];

  constructor(protected context: ExtensionContext) {
    context.subscriptions.push(languages.registerCompletionItemProvider('handlebars', this, '.', 'editor.action.triggerSuggest'));
  }

  provideCompletionItems(
    document: TextDocument,
    position: Position,
    _token: CancellationToken,
    _context: CompletionContext,
  ): ProviderResult<CompletionItem[] | CompletionList<CompletionItem>> {
    this.contexts = [];
    const result: Map<string, ModelItem> = new Map();
    // 当前光标所在位置的行
    const line = document.lineAt(position);
    // 行内容
    const lineText = line.text;
    // 行内光标前的内容
    const lineTextBeforeCursor = lineText.substring(0, position.character);
    const variable = lineTextBeforeCursor.substring(lineTextBeforeCursor.lastIndexOf('{{'), lineTextBeforeCursor.length);
    // 当是助手中时，获取助手中的第一组变量
    const reg = new RegExp(helperVariableReg).exec(variable) || /(?<={{)(.*)/.exec(variable);
    // 内容中是否包含开始标签
    if (variable.indexOf('{{') !== -1) {
      // 当为结束标签 或者 助手的前半部分时忽略
      if (variable.lastIndexOf('{{/') !== -1 || (variable.lastIndexOf('{{#') !== -1 && !reg)) {
        return [];
      }
      // 截取最后一个开始标签后的内容
      // 最后一个开始标签后的内容没有闭合
      if (variable.indexOf('}}') === -1) {
        // 计算文本当中所有的标签域
        this.calcContexts(document.getText());
        // 当次计算域中的总模型控制器
        const modelController = new ContextModel();
        // 加载全局模型
        const globalModel = this.model.getModel('/global');
        modelController.add(globalModel);
        // 光标的位置
        const offset = document.offsetAt(position);
        // 当前激活的标签域
        const active = this.contexts.find(ctx => ctx.isCurrentDomain(offset));
        if (active || (reg && reg[0].indexOf('.') !== -1)) {
          let tags: ContextTag[] = [];
          if (active) {
            // 根据便宜计算当前激活标签域下的所有上下文标签
            tags = active.calcCompletionDomain(offset);
            for (let i = 0; i < tags.length; i++) {
              const tag = tags[i];
              // 分割标签内变量
              const keys = tag.variable.split('.');
              for (let j = 0; j < keys.length; j++) {
                const key = keys[j];
                // 在作用域内查找变量的值
                const modelItem = modelController.getModel(key) as ModelItem;
                if (!modelItem) {
                  break;
                }
                // 如果指定的是 json 文件，去加载文件
                if (modelItem.schema && typeof modelItem.schema === 'string') {
                  const model = this.model.getModel(modelItem.schema);
                  modelItem.schema = model;
                }
                if (typeof modelItem.schema === 'object') {
                  modelController.add(modelItem.schema);
                }
              }
              // 如果有定义别名，在父级域下补充变量指向当前变量
              if (keys.length > 1 && tag.isAlias()) {
                const model = modelController.getModel(keys[keys.length - 1]);
                const parentModel = nth(modelController.models, -2)!;
                parentModel[tag.alias] = model;
              }
            }
          }
          // 默认填充全局模型
          let model: IModel = modelController.getGlobalModel();
          // 当前需要提示的区域变量
          let text = variable.substring(2, variable.length);
          if (reg) {
            text = reg[0];
          }
          // 分割当前准备填充的变量
          const keys = text.split('.');
          // 遍历当前变量的所有层级，未加载时。加载到堆栈中
          for (let i = 0; i < keys.length - 1; i++) {
            const key = keys[i];
            const modelItem = modelController.getModel(key) as IModel;
            if (modelItem && modelItem.schema) {
              if (typeof modelItem.schema === 'string') {
                const model = this.model.getModel(modelItem.schema);
                modelItem.schema = model;
              }
              modelController.add(modelItem.schema);
              model = modelItem.schema;
            } else {
              model = {};
            }
          }
          // 当前变量的最后一个层级
          const key = last(keys)!;
          // 提取模型中多有的 key 值，并填充到提示列表中
          const modelKeys = Object.keys(model);
          modelKeys.forEach(modelKey => {
            if (key !== '' && modelKey.indexOf(key) === -1) {
              return;
            }
            result.set(modelKey, model[modelKey]);
          });
          // 当 key 只有一个层级时
          if (keys.length === 1) {
            // 将上一个层级的模型添加到提示列表中
            const lastModel = modelController.getLastModel();
            Object.keys(lastModel).forEach(key => {
              result.set(key, lastModel[key]);
            });
            // 将别名添加到提示列表中
            tags.forEach(tag => {
              if (tag.isAlias()) {
                result.set(tag.alias, { desc: tag.alias, type: 'object' });
              }
            });
          }
        } else {
          // 无激活的标签域，则提示全局模型
          const model = modelController.getGlobalModel();
          Object.keys(model).forEach(key => {
            result.set(key, model[key]);
          });
        }
      }
    }
    const arr: ModelCompletionItem[] = [];
    result.forEach((value, key) => {
      arr.push(new ModelCompletionItem(key, value));
    });
    return arr;
  }

  /**
   * 计算文本中的闭合标签域
   *
   * @author chitanda
   * @date 2022-01-13 10:01:37
   * @protected
   * @param {string} text
   * @return {*}  {ContextTag[]}
   */
  protected calcContexts(text: string): ContextTag[] {
    const arr: ContextTag[] = [];
    let item: RegExpExecArray | null;
    // 匹配所有 each 开始
    const eachStart = new RegExp(eachTag);
    while ((item = eachStart.exec(text))) {
      arr.push(new ContextTag('each', item));
    }
    // 匹配所有 each 结束
    const eachEnd = new RegExp(eachEndTag);
    while ((item = eachEnd.exec(text))) {
      arr.push(new ContextTag('eachEnd', item));
    }
    // 匹配所有 with 开始
    const withStart = new RegExp(withTag);
    while ((item = withStart.exec(text))) {
      arr.push(new ContextTag('with', item));
    }
    // 匹配所有 with 结束
    const withEnd = new RegExp(withEndTag);
    while ((item = withEnd.exec(text))) {
      arr.push(new ContextTag('withEnd', item));
    }
    // 按位置进行排序
    arr.sort((a, b) => a.exec.index - b.exec.index);
    // 当前未闭合标签的个数
    let num = 0;
    // 一组标签域后置空
    const ctxs: ContextItem[] = [];
    // 计算每一个闭合的标签域
    for (let i = 0; i < arr.length; i++) {
      const item = arr[i];
      if (item.start) {
        // 没开始一个标签，新建并推送进堆栈
        num++;
        const ctx = new ContextItem();
        ctx.setStart(item);
        ctxs.push(ctx);
      } else if (item.end) {
        // 结束一个标签，从堆栈中取出设置结束标签并指给父级
        num--;
        // 获取最后一个未闭合的标签
        const lastCtx = last(ctxs)!;
        lastCtx.setEnd(item);
        if (ctxs.length >= 2) {
          // 从堆栈中移出已闭合的标签
          ctxs.pop();
          // 获取已闭合标签的父并添加到子
          const parentCtx = last(ctxs)!;
          parentCtx.addChildren(lastCtx);
        }
      }
      // 无未闭合标签，保存并清空堆栈
      if (num === 0 && ctxs.length > 0) {
        this.contexts.push(ctxs[0]);
        ctxs.splice(0, ctxs.length);
      }
    }
    return arr;
  }
}
